本文假设您对椭圆曲线运算及哈希函数等有着基础的了解
简洁的 Schnorr 协议
Alice 拥有一个秘密数字,a
,我们可以把这个数字想象成「私钥」,然后把它「映射」到椭圆曲线群上的一个点 a*G
,简写为 aG
。这个点我们把它当做「公钥」。
sk = a
( secret key = a )PK = aG
a secret key that corresponds to a public key .
请注意「映射」这个词,给任意一个有限域上的整数 r
,我们就可以在循环群中找到一个对应的点 rG
,或者用一个标量乘法来表示 r*G
。但是反过来计算是很「困难」的,这是一个「密码学难题」—— 被称为离散对数难题。
取模之后 , 就很难知道原来的指数是多少了。 事实上,如果模取得相当大,从运算结果倒推指数运算就不可行了;现代密码学很大程度上就是基于这个问题的“困难”
也就是说,如果任意给一个椭圆曲线循环群上的点 R
,那么到底是有限域中的哪一个整数对应 R
,这个计算是很难的,如果有限域足够大,比如说 256bit 这么大,我们姑且可以认为这个反向计算是不可能做到的
Schnorr 协议充分利用了有限域和循环群之间单向映射,实现了最简单的零知识证明安全协议:Alice 向 Bob 证明她拥有 PK
对应的私钥 sk
- 第一步:为了保证零知识,Alice 需要先产生一个随机数
r
,这个随机数的用途是用来保护私钥 无法被 Bob 抽取出来。这个随机数也需要映射到椭圆曲线群上即rG
。 ( 映射之后 , Bob 就不可能通过rG
推算出r
) - 第二步:Bob 要提供一个随机数进行挑战,我们把它称为
c
。 - 第三步:Alice 根据挑战数
c
计算z = r + c * a (即sk)
,把z
发给 Bob,Bob 在自己这边通过下式进行检验:
#![allow(unused)] fn main() { z*G ?= R + c*PK ?= rG + c*(aG) }
大家可以看到 Bob 在第三步「同态地」检验 z
的计算过程。如果这个式子成立,那么就能证明 Alice 确实有私钥 a
。可是,这是为什么呢?
z
的计算和验证过程很有趣,有几个关键技巧:
- 首先 Bob 必须给出一个「随机」挑战数 ,然后 Bob 在椭圆曲线上同态地检查
z
。如果我们把挑战数 看成是一个未知数,那么r+a*c=z
可以看成是一个一元一次方程,其中r
与a
是方程系数。请注意在c
未知的前提下,如果r + a*x = r' + a'*x
要成立,那么根据 Schwatz-Zippel 定理,极大概率上r=r'
,a=a'
都成立。也就是说, Alice 在c
未知的前提下,想找到另一对不同的r'
,a'
来计算z
骗过 Bob 是几乎不可能的。这个随机挑战数c
实现了r
和a
的限制。虽然 Bob 随机选了一个数,但是由于 Alice 事先不知道,所以 Alice 不得不使用私钥a
来计算z
。这里的关键:c
必须是个随机数。 - Bob 验证是在椭圆曲线群上完成。Bob 不知道
r
,但是他知道r
映射到曲线上的点R
;Bob 也不知道a
,但是他知道a
映射到曲线群上的点PK
,即a*G
。通过同态映射与Schwatz-Zippel 定理,Bob 可以校验z
的计算过程是否正确,从而知道 Alice 确实是通过r
和a
计算得出的z
,但是又不暴露r
与a
的值。 - 还有,在协议第一步中产生的随机数
r
保证了a
的保密性。因为任何一个秘密当和一个符合「一致性分布」的随机数相加之后的和仍然符合「一致性分布」。
看懂了这个图就看懂了 !!!!!
是 Sigma 零知识证明的一个特例
Schnorr 的非交互式版本
Schnorr 协议的非交互式版本可以避免 Prover 与 Verifier 的交互,但这要求 Prover 使用哈希函数,这样他就无法预测哈希函数的输出,非交互式版本的验证器实现非常简单,因为它不需要随机数生成器
(Making the protocol non-interactive)
首先定义: 是 即私钥 ; 是 Public key 即公钥 ;
- Prover 生成一个随机数 并创建一个承诺 , Prover 对 进行哈希处理以获得挑战值 ,
- Prover 创建对挑战的响应 , 然后将元组
(comm, s)
发送给验证者。
Verifier 自己计算 , 然后验证 :
如果 Verifier 自己验证这个等式相等, 则 Prover 就通过 这种方式隐藏了私钥 , 同时又能让对方确信自己真的有这个私钥 .
- The prover generates a random number
r
and creates a commitmentcom = gʳ
. The prover hashesg
,com
andy
to get challengec
.c = Hash(g, y, t)
. - The prover creates a response to the challenge as
s = r + c*x
. The prover sends tuple(t, s)
to the verifier.
The verifier now generates the same challenge c
as Hash(g, y, t)
and again checks if gˢ
equals yᶜ.t
. Python code demonstrating this protocol.
Schnorr 的问题
对不同的消息, 如果不幸选了相同的随机数 私钥就会泄露
如果 Alice 在两次交互过程中使用了同一个 K
,那么 Bob 可以通过发送两个不同的 c
和 c'
来得到 s
和 s'
,然后通过下面的公式算出私钥 a
:
s = (c +a*e)/k ,
s' = (c'+a*e)/k , 两式相减, 求出 k
k = (c - c')/(s - s')
a = (k * s - c)/e
ECDSA
Bitcoin 和 ETH 都支持 ECDSA signature.
why need ECDSA?
除了显而易见的“我需要对一份文件/合同进行签名”,还有一个非常流行的应用场景:让我们以一个不想自己的数据被用户修改或者破坏的应用程序为例,比如一个只允许你载入官方地图和不可修改的模块的游戏,或者一部只允许你安装官方应用程序的手机或其它设备。
在这些案例当中,相关文件(应用程序、游戏地图、数据等)会用 ECDSA
进行签名,公钥会随应用程序/游戏/设备一起捆绑并用来验证签名来确保数据没有被修改,而私钥在本地一个私密的地方进行保存。由于你可以用公钥对签名进行验证,但是不能用它创建或者伪造新的签名,你可以无所顾忌地将公钥随应用程序/游戏/设备一起分发。
这与AES
相比,区别是显而易见的。AES
加密系统允许你对数据进行加密,但是你需要用密钥来解密,这就要求你将密钥与应用程序一起捆绑,破坏了对数据进行保护防止数据被用户修改的目的。
一个很好的例子就是PS3的控制台,它被大量的破解,所有的文件可以解密,所有的密钥可以从解密的文件当中抽取,但是为了能够在最新的固件上面运行程序,你还需要破解一个ECDSA
的数字签名。
当你想要对一个文件进行签名的时候,你会用这个私钥 / 随机数 / 文件的哈希组成一个魔法数学方程,这将给出你的签名。签名本身将被分成两部分,称为 R
和 S
- 选择随机数 , 计算承诺 :
- 挑战 : 取 的横坐标为 (先 mod , 再 mod )
- 响应 :
为了验证签名的正确性,你只需要公钥(用私钥在曲线上面产生的点)并将公钥和签名的一部分 S
一起代入另外一个方程,如果这个签名是由私钥正确签名过的数字签名,那么它将给出签名的另外一部分 R
。
简单来说,一个数字签名包含两个数字,R
和 S
,然后你使用一个私钥来产生 R
和 S
,如果将公钥和 S
代入被选定的魔法数学方程给出 , 且 的话,这个签名就是有效的。仅仅知道公钥是无法知道私钥或者创建出数字签名。
Algorithm
初始化: 椭圆曲线生成元为 ,标量域为 ,基域为
基域 理解为椭圆曲线点的横纵坐标的取值范围 标量域 即做倍点运算的标量的取值范围, 比如 里的 , 其不会超过椭圆曲线的阶
密钥生成: 私钥 和公钥
签名: 输入任意消息 , 计算
- 选择随机数 , 计算承诺 :
- 挑战 : 取 的横坐标为 (先 mod , 再 mod )
- 响应 : ( k 增加了 ECDSA 的难度)
则签名为
是 的乘法逆元
我们是如何对一个文件或者一个信息进行签名的呢?
- 你需要知道签名本身是 40 字节,由各20字节的两个值来进行表示,第一个值叫作 ,第二个叫作 。
- 值对 放到一起就是你的
ECDSA
签名
验证 :
验证它,也非常的简单,你只需要 [公钥] 和导出这个公钥的曲线参数就可以了。你用以下方程来计算 :
Verifier :
- 输入消息 , 计算
- 校验
- 计算
取 的横坐标为 , 校验等式 : 如果相等, 则接受 , 否则拒绝
公式推导过程如下:
这里知道 还是可以推算私钥, 所以 EIP-32 要求 :
EdDSA
以太坊 BN256 曲线已经支持了 EdDSA
EdDSA 正是为了解决 Schnorr 签名私钥泄露的问题 : 他不是选择随机数, 而是计算随机数
初始化 : 椭圆曲线生成元为 , 阶为 密钥生成 : 私钥为 , 公钥为
签名: 消息为 ,计算随机数 **,计算承诺 ,
计算挑战
计算响应
签名为(R,s)
验证: 重新计算挑战 ,然后校验
与 ECDSA 最大的区别在于 是算出来的, 没有使用随机数 这样产生的签名结果是确定性的,即对同一消息, 签名结果相同, 不会额外泄露信息
一般说来随机数是安全措施中重要的一种方法,但是随机数的产生也是安全隐患,著名的索尼公司产品 PS3 密钥泄露事件,就是随机数产生的问题导致的 (写死在了代码里, 晕)。
zk-SNARK
在聊 zk-SNARKs 之前, 首先来看 NARK(Non-interactive ARgument of Knowledge) :
- C : 电路 Circuit
- : 公开声明 public statement
- : witness
- 预处理(Preprocessing) 也称为 Setup, 它以电路的描述作为输入,然后输出这些公开参数,我们称之为 和 :
- 表示公开的参数,供证明者使用。
- 表示公开的参数,供验证者使用。
证明者和验证者各自会输入 :
- prover takes the (public statement) & (public (circuit)params) & the Witness
- Verifier takes & (public statement)
然后,证明者试图向验证者证明: It knows some such that
NARK Definition : A pre-processing NARK is a triple , where :
- generate the Circuit’s as public params for P & V.
- : proof
- : or
所有算法和对手都可以访问 随机预言机 (random oracle)
zk-SNARKs 条件是苛刻的, 因为要让 Verifier 在如此短的时间内完成某些验证, 我们需要一些新的方法来去处理计算, 比如多项式承诺 (polynomial commitment)
(To be continued …)
Reference :
Vitalik ZK_Snark zk-learning Lectures 安比 zk-snarks https://vitalik.ca/general/2021/01/26/snarks.html Zero Knowledge Proofs with Sigma Protocols